《Effective Java》第五章:泛型
泛型,可以告诉编译器每个集合中接受哪些对象类型,编译器会自动地为你的插入进行转化,并在编译时告知是否插入了类型错误的对象。(貌似Java
里面的泛型都是伪泛型吧-_-|)
第23条:请不要再新代码中使用原生态类型
什么是原生态类型?
类似List<E>
,Collection<E>
泛型的定义都有一个原生态类型,那就是List
,Collection
,用他们定义类型也不会出错.
为什么不要使用原生态类型
使用原生态类型的集合类可以插入各种不同的类型,他们在编译时不会进行类型检查,但是运行时遇到错误就会抛出,这样是很不安全的。
我在不确定或者不在乎集合类型的情况下,是不是用原生态类型最好?
还是不要用,可以使用无限制的通配符来替代,比如1
2
3
4
5
6
7
8
9
10
11static int numElementsInCommon(Set<?> s1,Set<?> s2)
{
int count=0;
for(Object o:s1)
{
if(s2.contains(o))
count++;
}
return count;
}
它是类型安全的,但是无法猜测放入了哪些对象,此时可以使用泛型或者有限制的通配符类型(下文会描述)。
第24条:消除非首检警告
非受检警告很重要,每个警告都表示可能在运行时抛出
ClassCastException
异常,我们应该在编码时尽量消除编辑器给出的每一条受检警告。如果无法消除非首检警告,同时可证明引起警告的代码是类型安全的,就可以在尽量小得范围中,用@SuppressWarnings(“uncheck”)
注解来禁止该警告,并且要把禁止该警告的原因记录下来。
第25条:列表优先于数组
数组与泛型相比,有两个重要的不同点:
数组是协变的,泛型则是不可变的
如果Sub
为Super
的子类,那么数组类型Sub[]
就是Super[]
的子类,但是List<Sub>
与List<Super>
并没有什么卵关系。1
2
3
4
5Object[] objectArray=new Long[10];
objectArray[0]="Hello";//运行时会再这里抛ArrayStoreException异常
List<Object> list=new List<Long>();//在编译时就在这里出错了
list.add("Hello");从上面看出来,它们都不能将
String
放入Long
容器中,但是明显泛型的报错更加合理。
2.数组是具体化的,因此在运行时才会知道并检查它们元素类型的约束,而泛型是通过擦除来实现的,因此泛型时只在编译时强化它们的类信息,并在运行时丢弃。
泛型是数组不能很好的混合使用,使用泛型数组定义式非法的。
第26条:优先考虑泛型
使用泛型简单,但是自己编写泛型还是有一点麻烦的,下面是第6条中的两种泛型版本。
1.使用泛型E[]
来存储数据,但是用Object[]
的数组转换来处理泛型数组的创建错误1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40/**
* 简单的栈结构的实现 泛型版本-泛型数组转换
* @author yyl form Effective Java
*
*/
public class Stack<E> {
private E[] elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPCAITY=16;
/**
* 这个elements元素只会使用E类型来添加
* 所以他是足够安全的
*/
@SuppressWarnings("unchecked")
public Stack()
{
elements=(E[])new Object[DEFAULT_INITIAL_CAPCAITY];
}
public void push(E e)
{
ensureCapacity();//确保容量足够
elements[size++]=e;
}
public E pop()
{
if(size==0)
throw new EmptyStackException();
return elements[--size];//
}
private void ensureCapacity()
{
if(elements.length==size)
elements=Arrays.copyOf(elements, 2*size+1);
}
}
- 使用
Object[]
来存储,但是返回类型使用Object
向泛型类型E
去转换1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39/**
* 简单的栈结构的实现 泛型版本-泛型转换
* @author yyl form Effective Java
*
*/
public class Stack<E> {
private Object[] elements;
private int size=0;
private static final int DEFAULT_INITIAL_CAPCAITY=16;
public Stack()
{
elements=new Object[DEFAULT_INITIAL_CAPCAITY];
}
public void push(E e)
{
ensureCapacity();//确保容量足够
elements[size++]=e;
}
/**
* 这里都是需要返回E
* 所以这里的转换是安全的
*/
@SuppressWarnings("unchecked")
public E pop()
{
if(size==0)
throw new EmptyStackException();
return (E)elements[--size];//
}
private void ensureCapacity()
{
if(elements.length==size)
elements=Arrays.copyOf(elements, 2*size+1);
}
}
上述优先选择的是第二种方案,第一种禁止数组类型的非受检转换比禁止标量类型更加危险,并且它需要多次转换
E[]
,而第二是是多次转换E
第27条:优先考虑泛型方法
关于泛型方法和非泛型方法可以见下面:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 原生类型的书写,类型不安全,并且还会产生警告
*/
public static Set union2(Set s1,Set s2)
{
Set result=new HashSet(s1);
result.addAll(s2);
return result;
}
/**
* 泛型方法,类型安全
*/
public static <E> Set<E> union(Set<E> s1,Set<E> s2)
{
Set<E> result=new HashSet<E>(s1);
result.addAll(s2);
return result;
}
很明显,泛型方法更加安全,并且简单易用。
使用泛型方法就像类型异性,还应该将现有的方法泛型化,使新用户使用起来更加轻松,且不会平均破坏现有的客户端。
第28条:利用有限制通配符来提升API的灵活性
针对第26条中泛型Stack
的实现方法,如果现在需要添加pushAll
和popAll
这两个方法,为了让他们用起来更加顺手,那么应该这么写:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 所有E的子类的集合都可以添加进来
* @param iter
*/
public void pushAll(Iterable<? extends E> iter)
{
for(E e:iter)
this.push(e);
}
/**
* 可以将elements里面的元素全部弹出到E得超类中
* @param dst
*/
public void popAll(Collection<? super E> dst)
{
while(!isEmpty())
dst.add(pop());
}
使用通配符能让API变得更加灵活,在编写广泛使用的类库时,一定适当地利用通配符类型。
第29条:优先考虑类型安全的异构容器
泛型往往会被用户参数化了的容器,每个容器有固定数目的类型参数。但是你可能需要更加灵活地容器,比如数据库的表可以有任意多得列,在
Java
中,解决这个问题的做法就是将建参数化而不是容器参数化。可以使用类类型Class
来作为键值。
1 | static class Favorites{ |
这个Favorites
类型里面的键都不同类型的,这里将Favorites
称为异构容器。
它又两个局限性:
- 用户可以轻松地破坏
Favorites
的实例安全(可以用put
方法中cast
来避免) - 它只能用于可具体化的类ing中(比如List
这个类型你就不能传过去)